#include <utility>
#define __NO_STD_VECTOR
#include <CL/cl.hpp>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <string>
#include <iterator>
#include <time.h>

#define CONTEXT_GPU 1
#define CONTEXT_CPU 2

#define MY_WORKSIZE 120

void makeContext(cl::Context* _context, const unsigned char type = CONTEXT_GPU);
void makeProgram(const cl::Context* _context, cl::vector<cl::Device>* _devices, const char* filename, cl::Program* program);
void init(cl::Context* _context, cl::vector<cl::Device>* _devices, const char* filename, cl::Program* program);

float raw_data[MY_WORKSIZE];
float result_data[MY_WORKSIZE];

const char* getErrorDesc(int err);

inline void checkErr(cl_int err, const char * name)
{
    if (err != CL_SUCCESS) {
        std::cerr << "ERROR: " << name
                 << " (" << err << "). Output: " << getErrorDesc(err) << std::endl;
        exit(EXIT_FAILURE);
    }
}


int main()
{

	//Init Testing data
	unsigned int cnt;

	for(cnt = 0; cnt < MY_WORKSIZE; cnt++)
	{
		raw_data[cnt] = cnt;
	}


	for(cnt = 0; cnt < MY_WORKSIZE; cnt++)
	{
		result_data[cnt] = 444;
	}



	//open cl device / management stuff
	cl::Context context; //a collection of open cl memory regions, devices and program
	cl::Program program; //a collection of functions which can be runned on hardware
	cl::vector<cl::Device> devices; //a number of hardware where our functions can be executed on

	//DEBUG
	cl_int errorlevel;

	//init open cl. Get the gpu and compile double_number.cl so we can start it later on it. 
	//the double_number program must be started later explicitly
	init(&context, &devices, "double_number.cl", &program);

	//selecting the desired function(s) of the program to run on the hardware e.g. doubleNumbers() out of math.cl
	cl::Kernel kernel(program, "hello", &errorlevel);
	checkErr(errorlevel, "Kernel::Kernel()");

	//create Buffers for input / output for the hardware
	cl::Buffer inputBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, MY_WORKSIZE*sizeof(float), raw_data, &errorlevel);
	checkErr(errorlevel, "Buffer::Buffer() input");
	cl::Buffer outputBuffer(context, CL_MEM_WRITE_ONLY | CL_MEM_COPY_HOST_PTR, MY_WORKSIZE*sizeof(float), result_data, &errorlevel);
	checkErr(errorlevel, "Buffer::Buffer() output");

	//tell our function the address of the buffers like void doubleNumbers(int* input, int* output)
	errorlevel = kernel.setArg(0, inputBuffer);
	checkErr(errorlevel, "Kernel::setArg() input");

	errorlevel = kernel.setArg(1, outputBuffer);
	checkErr(errorlevel, "Kernel::setArg() output");

	//create an execute-queue for the device(s), a must have in open cl
	cl::CommandQueue queue(context, devices[0], 0, &errorlevel);
	checkErr(errorlevel, "CommandQueue::CommandQueue()");

	//create an event where the progress of the executeable is logged
	cl::Event event;

	//put our function into the execute-queue
	errorlevel = queue.enqueueNDRangeKernel(kernel, cl::NullRange, cl::NDRange(MY_WORKSIZE*sizeof(float)), cl::NDRange(1, 1), NULL, &event);
	checkErr(errorlevel, "CommandQueue::enqueueNDRangeKernel()");

	//wait until the executeable is finished
	event.wait();

	//read the data from the hardware back to our cpu memory
	errorlevel = queue.enqueueReadBuffer(outputBuffer, CL_TRUE, 0, MY_WORKSIZE*sizeof(float), result_data);    
	checkErr(errorlevel, "CommandQueue::enqueueReadBuffer()");

	//Output Testing data
	for(cnt = 0; cnt < MY_WORKSIZE; cnt++)
	{
		std::cout << "Element " << cnt << ": " << result_data[cnt] << std::endl;
	}
	
	return(0);
}


void init(cl::Context* _context, cl::vector<cl::Device>* _devices, const char* filename, cl::Program* _program)
{
	//setup stuff
	makeContext(_context, CONTEXT_GPU);

	*_devices = _context->getInfo<CL_CONTEXT_DEVICES>(); 
	checkErr(_devices->size() > 0 ? CL_SUCCESS : -1, "devices.size() > 0");

	makeProgram(_context, _devices, filename, _program);

}

void makeProgram(const cl::Context* _context, cl::vector<cl::Device>* _devices, const char* filename, cl::Program* program)
{
	cl_int errorlevel;
	std::ifstream file(filename);
	checkErr(file.is_open() ? CL_SUCCESS:-1, filename);
 
	std::string prog(std::istreambuf_iterator<char>(file), (std::istreambuf_iterator<char>()));	

	cl::Program::Sources source(1, std::make_pair(prog.c_str(), prog.length()+1));
	*(program) = cl::Program(*_context, source);
	errorlevel = program->build(*_devices,""); 

	cl::STRING_CLASS buildLog;
	program->getBuildInfo((*_devices)[0], CL_PROGRAM_BUILD_LOG, &buildLog);
	std::cout << "Build Log: \n" << buildLog.c_str() << std::endl;
}

void makeContext(cl::Context* _context, const unsigned char type)
{
	cl_int errorlevel;
	cl::vector <cl::Platform> platformVector;
	cl::Platform::get(&platformVector);
	checkErr(platformVector.size()!=0 ? CL_SUCCESS : -1, "cl::Platform::get");


	std::string platformVendor;

	cl_context_properties cprops[3] = {CL_CONTEXT_PLATFORM, (cl_context_properties)(platformVector[0])(), 0};
	
	*(_context) = cl::Context(CL_DEVICE_TYPE_GPU, cprops, NULL, NULL, &errorlevel);
	checkErr(errorlevel, "Conext::Context()");  

}

const char* getErrorDesc(int err)
{
        switch (err)
	{
            case CL_SUCCESS: return "";
            case CL_DEVICE_NOT_FOUND               : return "CL_DEVICE_NOT_FOUND";
            case CL_COMPILER_NOT_AVAILABLE         : return "CL_COMPILER_NOT_AVAILABLE";
            case CL_MEM_OBJECT_ALLOCATION_FAILURE  : return "CL_MEM_OBJECT_ALLOCATION_FAILURE";
            case CL_OUT_OF_RESOURCES               : return "CL_OUT_OF_RESOURCES";
            case CL_OUT_OF_HOST_MEMORY             : return "CL_OUT_OF_HOST_MEMORY";
            case CL_PROFILING_INFO_NOT_AVAILABLE   : return "CL_PROFILING_INFO_NOT_AVAILABLE";
            case CL_MEM_COPY_OVERLAP               : return "CL_MEM_COPY_OVERLAP";
            case CL_IMAGE_FORMAT_MISMATCH          : return "CL_IMAGE_FORMAT_MISMATCH";
            case CL_IMAGE_FORMAT_NOT_SUPPORTED     : return "CL_IMAGE_FORMAT_NOT_SUPPORTED";
            case CL_BUILD_PROGRAM_FAILURE          : return "CL_BUILD_PROGRAM_FAILURE";
            case CL_MAP_FAILURE                    : return "CL_MAP_FAILURE";

            case CL_INVALID_VALUE                  : return "CL_INVALID_VALUE";
            case CL_INVALID_DEVICE_TYPE            : return "CL_INVALID_DEVICE_TYPE";
            case CL_INVALID_PLATFORM               : return "CL_INVALID_PLATFORM";
            case CL_INVALID_DEVICE                 : return "CL_INVALID_DEVICE";
            case CL_INVALID_CONTEXT                : return "CL_INVALID_CONTEXT";
            case CL_INVALID_QUEUE_PROPERTIES       : return "CL_INVALID_QUEUE_PROPERTIES";
            case CL_INVALID_COMMAND_QUEUE          : return "CL_INVALID_COMMAND_QUEUE";
            case CL_INVALID_HOST_PTR               : return "CL_INVALID_HOST_PTR";
            case CL_INVALID_MEM_OBJECT             : return "CL_INVALID_MEM_OBJECT";
            case CL_INVALID_IMAGE_FORMAT_DESCRIPTOR: return "CL_INVALID_IMAGE_FORMAT_DESCRIPTOR";
            case CL_INVALID_IMAGE_SIZE             : return "CL_INVALID_IMAGE_SIZE";
            case CL_INVALID_SAMPLER                : return "CL_INVALID_SAMPLER";
            case CL_INVALID_BINARY                 : return "CL_INVALID_BINARY";
            case CL_INVALID_BUILD_OPTIONS          : return "CL_INVALID_BUILD_OPTIONS";
            case CL_INVALID_PROGRAM                : return "CL_INVALID_PROGRAM";
            case CL_INVALID_PROGRAM_EXECUTABLE     : return "CL_INVALID_PROGRAM_EXECUTABLE";
            case CL_INVALID_KERNEL_NAME            : return "CL_INVALID_KERNEL_NAME";
            case CL_INVALID_KERNEL_DEFINITION      : return "CL_INVALID_KERNEL_DEFINITION";
            case CL_INVALID_KERNEL                 : return "CL_INVALID_KERNEL";
            case CL_INVALID_ARG_INDEX              : return "CL_INVALID_ARG_INDEX";
            case CL_INVALID_ARG_VALUE              : return "CL_INVALID_ARG_VALUE";
            case CL_INVALID_ARG_SIZE               : return "CL_INVALID_ARG_SIZE";
            case CL_INVALID_KERNEL_ARGS            : return "CL_INVALID_KERNEL_ARGS";
            case CL_INVALID_WORK_DIMENSION         : return "CL_INVALID_WORK_DIMENSION";
            case CL_INVALID_WORK_GROUP_SIZE        : return "CL_INVALID_WORK_GROUP_SIZE";
            case CL_INVALID_WORK_ITEM_SIZE         : return "CL_INVALID_WORK_ITEM_SIZE";
            case CL_INVALID_GLOBAL_OFFSET          : return "CL_INVALID_GLOBAL_OFFSET";
            case CL_INVALID_EVENT_WAIT_LIST        : return "CL_INVALID_EVENT_WAIT_LIST";
            case CL_INVALID_OPERATION              : return "CL_INVALID_OPERATION";
            case CL_INVALID_GL_OBJECT              : return "CL_INVALID_GL_OBJECT";
            case CL_INVALID_BUFFER_SIZE            : return "CL_INVALID_BUFFER_SIZE";
            case CL_INVALID_MIP_LEVEL              : return "CL_INVALID_MIP_LEVEL";
            default: return "Unknown Error";
        }
}

